
package com.wlsk.license.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wlsk.license.dto.request.PlateOwnerReqDto;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * sm4加密算法工具类 sm4加密、解密与加密结果验证 可逆算法
 *
 * @author AmyOlive
 *
 */
public class Sm4Util {

  static {
    Security.addProvider(new BouncyCastleProvider());
  }

  private static final String ENCODING = "UTF-8";

  public static final String ALGORITHM_NAME = "SM4";

  // 加密算法/分组加密模式/分组填充方式
  // PKCS5Padding-以8个字节为一组进行分组加密
  // 定义分组加密模式使用：PKCS5Padding
  public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";

  // 128-32位16进制；256-64位16进制
  public static final int DEFAULT_KEY_SIZE = 128;

  /**
   * 生成ECB暗号
   *
   * @explain ECB模式（电子密码本模式：Electronic codebook）
   * @param algorithmName
   *          算法名称
   * @param mode
   *          模式
   * @param key
   *          密钥值
   * @return
   */
  private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key)
      throws Exception {
    Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
    Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
    cipher.init(mode, sm4Key);
    return cipher;
  }

  /**
   * 自动生成密钥
   *
   * @explain
   * @return
   */
  public static String generateKey() throws Exception {
    return generateKey(DEFAULT_KEY_SIZE);
  }

  /**
   * 生成密钥
   *
   * @explain
   * @param keySize
   *          大小
   * @return
   */
  public static String generateKey(int keySize) throws Exception {
    // KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME,
    // BouncyCastleProvider.PROVIDER_NAME);
    // kg.init(keySize, new SecureRandom());
    // return kg.generateKey().getEncoded();

    KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
    kg.init(keySize, new SecureRandom());
    return new String(Hex.encodeHex(kg.generateKey().getEncoded())).toUpperCase();
  }

  /**
   * sm4加密
   *
   * @explain 加密模式：ECB 密文长度不固定，会随着被加密字符串长度的变化而变化
   * @param hexKey
   *          16进制密钥（忽略大小写）
   * @param paramStr
   *          待加密字符串
   * @return 返回16进制的加密字符串
   */
  public static String encryptEcb(String hexKey, String paramStr) throws Exception {
    String cipherText = "";
    // 16进制字符串-->byte[]
    byte[] keyData = ByteUtils.fromHexString(hexKey);
    // String-->byte[]
    byte[] srcData = paramStr.getBytes(ENCODING);
    // 加密后的数组
    byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
    // byte[]-->hexString
    cipherText = ByteUtils.toHexString(cipherArray);
    return cipherText;
  }

  /**
   * 加密模式之Ecb
   *
   * @explain
   * @param key
   *          主键
   * @param data
   *          值
   * @return
   */
  public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
    Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
    return cipher.doFinal(data);
  }

  /**
   * sm4解密
   *
   * @explain 解密模式：采用ECB
   * @param hexKey
   *          16进制密钥
   * @param cipherText
   *          16进制的加密字符串（忽略大小写）
   * @return 解密后的字符串
   */
  public static String decryptEcb(String hexKey, String cipherText) throws Exception {
    // 用于接收解密后的字符串
    String decryptStr = "";
    // hexString-->byte[]
    byte[] keyData = ByteUtils.fromHexString(hexKey);
    // hexString-->byte[]
    byte[] cipherData = ByteUtils.fromHexString(cipherText);
    // 解密
    byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
    // byte[]-->String
    decryptStr = new String(srcData, ENCODING);
    return decryptStr;
  }

  /**
   * 解密
   *
   * @explain
   * @param key
   *          密钥
   * @param cipherText
   *          加密后文本
   * @return
   */
  public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
    Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
    return cipher.doFinal(cipherText);
  }

  /**
   * 校验加密前后的字符串是否为同一数据
   *
   * @explain
   * @param hexKey
   *          16进制密钥（忽略大小写）
   * @param cipherText
   *          16进制加密后的字符串
   * @param paramStr
   *          加密前的字符串
   * @return 是否为同一数据
   */
  public static boolean verifyEcb(String hexKey, String cipherText, String paramStr)
      throws Exception {
    // 用于接收校验结果
    boolean flag = false;
    // hexString-->byte[]
    byte[] keyData = ByteUtils.fromHexString(hexKey);
    // 将16进制字符串转换成数组
    byte[] cipherData = ByteUtils.fromHexString(cipherText);
    // 解密
    byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
    // 将原字符串转换成byte[]
    byte[] srcData = paramStr.getBytes(ENCODING);
    // 判断2个数组是否一致
    flag = Arrays.equals(decryptData, srcData);
    return flag;
  }

  public static void main(String[] args) throws Exception {
    //===请求报文===
// 请求JSON的body报文
    String reqBodyJson = "{\"orderNo\",\"2018091814591100000001\"}";
// sm4加密
    String key = "C537A8C72350A2F8966DDAFD16D651E7";//（服务端提供，双方使用一样的key进行加解密）
    String body = encryptEcb(key, reqBodyJson);
    System.out.println("body:"+ body);

//===验证请求报文===
//解密
    String reqBodyJson1 = decryptEcb(key, body);
    System.out.println("reqBodyJson1:" + reqBodyJson1);
//验证
    boolean isSm4True = verifyEcb(key, body, reqBodyJson1);
    System.out.println("isSm4True:"+isSm4True);// true


    Map map=new HashMap();
    map.put("orderNo","2018091814591100000001");
    System.out.println(map.toString());
    PlateOwnerReqDto reqDto=new PlateOwnerReqDto();
    reqDto.setLiceno("2018091814591100000001");
    String ss= JSONObject.toJSONString(reqDto);
    String ss2= JSON.toJSONString(reqDto);
    System.out.println(JSONObject.toJSON(reqDto));
    PlateOwnerReqDto parse = JSON.parseObject(ss, PlateOwnerReqDto.class);
    System.out.println("----");
  }
}